<!--
tinymce 富文本编辑器组件
@author yrf
@created 2019/09/16 10:56
-->
<template>
  <div>
    <div>
      <textarea :id="editorId" :key="tinymceFlag" />
    </div>
  </div>
</template>

<script>
import Scriptjs from 'scriptjs'
import request from '@/utils/request'
import { formatURL } from './utils'
import { v4 as uuidv4 } from 'uuid'
export default {
  name: 'QUPTinyEditor',
  model: {
    prop: 'content',
    event: 'change'
  },
  props: {
    /* 编辑器id, 多个编辑器时使用 */
    editorId: {
      type: String,
      default: `tiny-mce-editor-${uuidv4()}`
    },
    /* 编辑器初始化配置参数对象 */
    editorConf: {
      type: Object,
      default() {
        return {}
      }
    },
    /* 编辑器模式，readonly(只读)/design(编辑) */
    mode: {
      type: String,
      default: 'design'
    },
    /* 富文本编辑器显示的内容 */
    content: {
      type: String,
      default: ''
    },
    placeholder: {
      type: String,
      default: '请输入'
    },
    /* 富文本编辑器初始显示内容 */
    initContent: {
      type: String,
      default: ''
    }, // TODO:是否必须存在
    /* 富文本中上传图片到图片库时额外附带的参数 */
    uploadImgData: {
      type: Object,
      default() {
        return {
          /* pictureType: '',
          scene: 'prodDetail',
          source: 'TinyMCE Editor' */
        }
      }
    },
    imageAccept: {
      type: Array,
      default: () => {
        return ['.png', '.jpg', '.jpeg', '.gif', '.bmp']
      }
    }, // 允许上传的图片类型
    videoAccept: {
      type: Array,
      default: () => {
        return ['.mp3', '.mp4', '.ogv']
      }
    }, // 允许上传的视频类型
    fileAccept: {
      type: Array,
      default: () => {
        return ['.zip', '.rar', '.7z', '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx']
      }
    }, // 允许上传的文件类型

    filePrefix: {
      type: String,
      default: ''
    }, // 上传文件前缀
    imageSize: {
      type: Number,
      default: 2// 默认2M
    }, // 上传图片的大小限制
    beforeUpload: {
      type: Function,
      default: () => {
        return true
      }
    }, // 上传前的处理
    uploadResField: {
      type: String,
      default: 'file'
    }, // 上传返回字段
    uploadUrl: {
      type: String,
      default: '/api/v1/file/upload'
    }, // 上传url地址
    customButtons: {
      type: Array,
      default: () => [] // [{code,name,text,tooltip,onActive}]
    } // 自定义按钮
  },
  data() {
    return {
      // tinymce: null,
      tinymceFlag: 1,
      editor: null,
      dialogPicUpload: false,
      hasInit: false,
      hasChange: false
      // changeFromParentFlag: false
    }
  },
  watch: {
    mode: {
      handler(val) {
        this.setMode(val)
      }
    },
    // 由于双向绑定存在光标位置跳回首位问题，所以监听initContent参数
    initContent: {
      handler(val) {
        this.setContent(val)
      }
    }
  },
  activated() {
    this.tinymceFlag++
  },
  mounted() {
    window.vm = this
    this.initEidtor()
  },
  beforeDestroy() {
    if (this.editor) {
      this.editor.destroy()
    }
  },
  methods: {
    /**
     * 图库中选中图片后插入到富文本编辑器中
     * @param imgs
     */
    selectedPics(imgs) {
      // console.log(imgs)
      for (let i = 0; i < imgs.length; i++) {
        const pic = imgs[i]
        this.editor.insertContent(
          `<img alt="${pic.name}" src="https://testimg.quanyou.com.cn/${pic.picturePath}"/>`
        )
      }
    },
    /**
     * 初始化编辑器
     */
    initEidtor() {
      Scriptjs(process.env.BASE_URL + '/cdn/tinymce/tinymce.min.js',
        () => {
          let defaultToolBar = 'undo redo removeformat | cut copy paste | fontsizeselect forecolor backcolor | bold italic underline strikethrough | alignleft alignright aligncenter alignjustify alignnone | bullist numlist  | outdent indent blockquote  codesample | link unlink | image axupimgs media | emoticons  code  fullscreen preview '
          const customToolBar = this.customButtons.map(v => {
            return v.code
          })
          if (customToolBar.length) {
            defaultToolBar = `${defaultToolBar} | ${customToolBar.join(' ')}`
          }

          const conf = Object.assign(
            {
              selector: '#' + this.editorId,
              height: 500,
              language: 'zh_CN',
              menubar: true, // 设置富文本编辑器菜单, 默认true
              branding: false, // 是否关闭底部官网提示 默认true
              statusbar: true, // 显示底部状态栏 默认true

              readonly: false, // 设置只读属性 默认 false
              resize: false, // 调节编辑器大小 默认 true

              autosave_ask_before_unload: false, // 当关闭或跳转URL时，弹出提示框提醒用户仍未保存变更内容。 默认 true  需引入插件autosave
              autosave_interval: '3s', // 设置自动保存为草稿时间 单位只能为s 需引入插件autosave
              autosave_prefix: `editor_${this.$route.path}`, // 设置自动保存为草稿时前缀 本地localStorage中存储  需引入插件autosave
              autosave_retention: '300m', // 自动草稿的有效期 单位只能为m  需引入插件autosave

              browser_spellcheck: true, // 拼写检查
              elementpath: true, // 是否显示编辑器底部左侧元素路径
              paste_data_images: true, // 是否允许粘贴图像
              contextmenu: 'copy paste cut link', // 上下文菜单 默认 false
              draggable_modal: true, // 模态框拖动 默认false
              placeholder: this.placeholder, // 占位符
              theme: 'silver', // 主题 必须引入
              content_css: `/tinymce/skins/content/default`, // 富文本编辑器内容区域样式
              content_style: 'body, p{font-size: 14px}', // 为内容区编辑自定义css样式
              fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 26px 36px 48px 56px', // 工具栏自定义字体大小选项
              font_formats: "微软雅黑='微软雅黑'; 宋体='宋体'; 黑体='黑体'; 仿宋='仿宋'; 楷体='楷体'; 隶书='隶书'; 幼圆='幼圆'; 方正舒体='方正舒体'; 方正姚体='方正姚体'; 等线='等线'; 华文彩云='华文彩云'; 华文仿宋='华文仿宋'; 华文行楷='华文行楷'; 华文楷体='华文楷体'; 华文隶书='华文隶书'; Andale Mono=andale mono,times; Arial=arial; Arial Black=arial black;avant garde; Book Antiqua=book antiqua;palatino; Comic Sans MS=comic sans ms; Courier New=courier new;courier; Georgia=georgia; Helvetica=helvetica; Impact=impact;chicago; Symbol=symbol; Tahoma=tahoma;arial; sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms; Verdana=verdana;geneva; Webdings=webdings; Wingdings=wingdings", // 工具栏自定义字体选项
              toolbar_sticky: true, // 粘性工具栏 默认false (在向下滚动网页直到不再可见编辑器时，将工具栏和菜单停靠在屏幕顶部)
              toolbar_mode: 'sliding', // sliding生效条件toolbar必须为字符串,且有'|'区分,不能为数组
              fullscreen_native: true, // 设为true后，全屏将不在是网页全屏，而是使用浏览器的全屏模式（同按F11键）,需引入插件fullscreen
              mediaembed_max_width: 450,
              plugins: 'autosave,advlist,image,axupimgs,media,link,table,fullscreen,preview,lists,wordcount,code,emoticons', // mediaembed(增强媒体嵌入),advcode(高级代码)
              toolbar: defaultToolBar, // | myCustomToolbarButton myCustomMedia submit clearContent
              // 初始化前执行
              setup: (editor) => {
                // 监听鼠标按下事件
                editor.on('keydown', (e) => {
                  if (e.keyCode === 9) {
                    if (e.shiftKey) {
                      editor.execCommand('Outdent')
                    } else {
                      editor.execCommand('Indent')
                    }
                    e.preventDefault()
                    e.stopPropagation()
                  }
                })
                // 注册自定义操作按钮
                this.customButtons.forEach(v => {
                  if (v.code) {
                    const btnObj = {
                      text: v.text,
                      onAction: (editor) => {
                        v.onAction && v.onAction(editor)
                      }
                    }
                    if (v.tooltip) {
                      btnObj.tooltip = v.tooltip
                    }
                    editor.ui.registry.addButton(v.code, btnObj)
                  }
                })
                /* // 注册自定义上传按钮
                editor.ui.registry.addButton('myCustomToolbarButton', {
                  text: '<i class="el-icon-upload" style="font-size: 24px"></i>',
                  tooltip: '从图库选择图片',
                  onAction: () => {
                    this.openPicStock()
                  }
                })
                // 注册自定义上传按钮
                editor.ui.registry.addButton('myCustomMedia', {
                  text: '上传视频',
                  onAction: () => {
                    this.openPicStock()
                  }
                })
                // 注册获取内容按钮
                editor.ui.registry.addButton('submit', {
                  text: `<i class="el-icon-position" style="font-size: 18px"></i>`,
                  tooltip: '获取内容',
                  onAction: () => {
                    console.log(editor.getContent())
                  }
                })
                // 注册清空内容按钮
                editor.ui.registry.addButton('clearContent', {
                  text: `<i class="el-icon-close" style="font-size: 18px"></i>`,
                  tooltip: '清空内容',
                  onAction: () => {
                    console.log('清空富文本内容')
                    this.content = ''
                    editor.setContent('')
                  }
                })*/
              },
              // 设置事件回调函数 ,初始化结束后执行
              init_instance_callback: (editor) => {
                // 里面实现双向数据绑定功能
                if (this.value) {
                  editor.setContent(this.value)
                }

                this.hasInit = true
                editor.on('Input undo redo Change execCommand SetContent', (e) => {
                  this.hasChange = true
                  // editor.getContent({ format: ''text }) // 获取纯文本
                  this.$emit('change', editor.getContent())
                })

                /* // 使用箭头函数，this指向当前vue实例
                  editor.on('Change', () => {
                    this.$emit('change', this.getContent())
                  })
                  editor.on('SetContent', (e) => {
                    console.log('content', e.content)
                    this.$emit('change', e.content)
                    // if (this.changeFromParentFlag === true){
                    //     this.changeFromParentFlag = false
                    //   } else {
                    //     // 该变化不是由父组件触发，则向上反馈改变
                    //     this.$emit('change', e.content)
                    //   }
                  }) */
              },

              // 图片回调url的相对路径，可写可不写，为兼容图片回调而设
              image_prepend_url: `${this.filePrefix}`, // 指定图片URL补齐前缀
              axupimgs_filetype: this.imageAccept.join(','), // 设置允许上传的图片类型
              // 自定义前端上传逻辑
              images_upload_handler: (blobInfo, success, failure) => {
                const file = blobInfo.blob()
                const contentType = file.type // 文件类型：image/png
                const suffix = contentType.split('/')[1] // 文件后缀
                if (!this.imageAccept.includes(`.${suffix}`)) {
                  this.$message.warning({
                    message: `上传资源只能是${this.imageAccept.join(',')} 格式!`,
                    duration: 5000,
                    customClass: 'zZindex'
                  })
                  return false
                }
                this.uploadToServer('image', file, success, failure)
              },

              file_picker_types: 'image media', // 'file image media' 只允许图片和视频自定义上传则去掉file,加上file则链接也可上传
              // 自定义文件上传逻辑
              file_picker_callback: (callback, value, meta) => {
                /* // Provide file and text for the link dialog
                  if (meta.filetype === 'file') {
                    callback('mypage.html', { text: 'My text' })
                  }
                  // Provide image and alt text for the image dialog
                  if (meta.filetype === 'image') {
                    callback('myimage.jpg', { alt: 'My alt text' })
                  }
                  // Provide alternative source and posted for the media dialog
                  if (meta.filetype === 'media') {
                    callback('movie.mp4', { source2: 'alt.ogg', poster: 'image.jpg' })
                  } */
                const metaType = meta.filetype // 插件类型
                // const metaFieldname = meta.fieldname // 插件字段名称
                console.log(meta, value)
                // 文件分类
                let filetype = this.fileAccept.concat(this.videoAccept).join(',')
                // '.pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4'
                // 为不同插件指定文件类型
                switch (metaType) {
                  case 'image':
                    filetype = this.imageAccept.join(',') // '.jpg, .jpeg, .png, .gif'
                    // filetype = 'image/gif,image/jpeg,image/png,image/jpg'
                    break
                  case 'media':
                    filetype = this.videoAccept.join(',') // '.mp3, .mp4,.ogv'
                    // filetype = 'video/mp4,audio/mpeg,video/ogg,video/AVI,video/mov,video/FLV,video/rmvb,video/mtv,video/mpeg'
                    break
                  case 'file':
                    filetype = this.fileAccept.join(',')
                    break
                  default:
                }
                // 模拟出一个input用于添加本地文件
                var input = document.createElement('input')
                input.setAttribute('type', 'file')
                input.setAttribute('accept', filetype)
                input.setAttribute('id', 'inputfile')
                input.click()
                input.onchange = (e) => {
                  const cInput = e.path[0]
                  const file = cInput.files[0] // 文件
                  const fileName = file.name
                  const contentType = file.type // 文件类型：image/png 、 video/mp4
                  const nameDots = fileName.split('.')
                  const suffix = nameDots[nameDots.length - 1] // 文件后缀
                  console.log(fileName, contentType, suffix, filetype.indexOf(suffix))
                  if (filetype.indexOf(suffix) === -1) {
                    this.$message.warning({
                      message: `上传资源只能是${filetype} 格式!`,
                      duration: 5000,
                      customClass: 'zZindex'
                    })
                    return false
                  }

                  this.uploadToServer(metaType, file, (url) => {
                    // 上传成功
                    url = formatURL(url, this.filePrefix)
                    if (metaType === 'file') {
                      callback(url, { text: file.name })
                    } else if (metaType === 'image') {
                      callback(url, { alt: file.name })
                    } else if (metaType === 'media') {
                      // callback(url, { source2: 'alt.ogg', poster: 'image.jpg' })
                      callback(url, { source2: '', poster: '' })
                    }
                  }, (error) => {
                    // 上传失败
                    console.log('Error: ' + error)
                    callback(file, { alt: file.name })
                  })

                  // 下方被注释掉的是官方的一个例子,供参考
                  /* var reader = new FileReader();
                      reader.onload = function () {
                          // Note: Now we need to register the blob in TinyMCEs image blob
                          // registry. In the next release this part hopefully won't be
                          // necessary, as we are looking to handle it internally.
                          var id = 'blobid' + (new Date()).getTime();
                          var blobCache =  tinymce.activeEditor.editorUpload.blobCache;
                          var base64 = reader.result.split(',')[1];
                          var blobInfo = blobCache.create(id, file, base64);
                          blobCache.add(blobInfo);

                          // call the callback and populate the Title field with the file name
                          callback(blobInfo.blobUri(), { title: file.name });
                      };
                      reader.readAsDataURL(file);*/
                }
              }
            },
            this.editorConf
          )
          window.tinymce
            .init(conf)
            .then((editors) => {
              this.editor = editors[0]
              if (this.content || this.initContent) {
                this.setContent(this.content || this.initContent)
              }
              this.setMode(this.mode)
            })
            .catch((error) => {
              this.$message.error('富文本编辑器初始化失败: ' + error)
            })
        }
      )
    },
    /**
     * 上传文件至服务器
     * @param {string} metaType 上传类型：image 图片 ，video 视频 , file 文件
     * @param {file} file 需上传的文件
     * @param {Function} success 上传成功的回调
     * @param {Function} failure 上传失败的回调
    */
    uploadToServer(metaType, file, success, failure) {
      if (metaType === 'image') {
        // 若为上传图片，限制上传大小
        const isLt2M = file.size / 1024 / 1024 <= this.imageSize
        if (!isLt2M) {
          this.$notify.error({
            title: '错误',
            offset: 100,
            message: `上传图片大小不能超过 ${this.imageSize}MB!`
          })
          return false
        }
      }
      const flag = this.beforeUpload(metaType, file)
      if (!flag) {
        return false
      }

      const formData = new FormData() // 创建form表单
      formData.append('file', file)
      for (const key in this.uploadImgData) { // 添加其他form表单数据
        formData.append(key, this.uploadImgData[key])
      }
      const loading = this.$loading({
        lock: true,
        text: '正在上传...',
        spinner: 'el-icon-loading',
        background: 'rgba(0, 0, 0, 0.7)'
      })
      request.post(this.uploadUrl, formData).then((result) => {
        const url = (result.data && result.data[this.uploadResField]) || ''
        if (!url) {
          this.$message.error({
            message: `上传结果异常!`,
            duration: 3000
          })
          return
        }
        success(url)
        loading.close()
      }).catch((error) => {
        failure('Error: ' + error)
        this.$message.error('上传失败')
        loading.close()
      }).finally((ff) => {
        loading.close()
      })
    },

    /**
     * 插入内容
     * @param {string} type 内容类型： image 图片, video 视频，audio 音频
     * @param {string} content 内容/文件地址
     * */
    insertContent(type, content) {
      let htmlContent = content
      if (type === 'image') {
        htmlContent = `<img src="${content}" style="max-width:100%;"/>`
      } else if (type === 'video') {
        htmlContent = `<video src="${content}" 
            webkit-playsinline="true" 
            x5-video-player-type="h5" 
            x5-video-orientation="portraint" 
            controls="controls" style="max-width:100%"></video><p><br></p>`
      } else if (type === 'audio') {
        htmlContent = `<audio src="${content}" controls="controls" style="max-width:100%"></audio><p><br></p>`
      }
      this.editor.insertContent(htmlContent)
    },
    /**
     * 获取编辑器中的html代码
     * @return {*}
     */
    getContent() {
      return this.editor.getContent()
    },
    /**
     * 保存并获取编辑器中的html代码
     * @return {*}
     */
    save() {
      return this.editor.save()
    },
    /**
     * 设置富文本编辑器展示的内容
     * @param content
     */
    setContent(content) {
      if (this.editor) {
        this.editor.setContent(content)
      } else {
        console.warn('编辑器尚未初始化')
      }
    },
    /**
     * 设置富文本编辑器模式
     * @param mode 模式值，readonly(只读)/design(编辑)
     */
    setMode(mode) {
      if (this.editor) {
        this.editor.setMode(mode)
      } else {
        console.warn('编辑器尚未初始化')
      }
    }
  }
}
</script>

<style lang='scss' scoped>
/deep/ .mce-content-body img{
  max-width: 400px;
 }
</style>
